use byteorder::{ByteOrder, NetworkEndian};
use core::fmt;

use crate::{Error, Result};

pub use super::EthernetProtocol as Protocol;

enum_with_unknown! {
    /// EAP Code.
    pub enum Code(u8) {
        Request = 1,
        Response = 2,
        Success = 3,
        Failure = 4
    }
}

/// A read/write wrapper around an Address Resolution Protocol packet buffer.
#[derive(Debug, PartialEq, Clone)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
pub struct Packet<T: AsRef<[u8]>> {
    buffer: T,
}

mod field {
    #![allow(non_snake_case)]

    use crate::wire::field::*;

    pub const CODE: usize = 0;
    pub const IDENT: usize = 1;
    pub const LEN: Field = 2..4;
    pub const DATA: Rest = 4..;
}

pub const HEADER_LEN: usize = field::DATA.start;

impl<T: AsRef<[u8]>> Packet<T> {
    /// Imbue a raw octet buffer with ARP packet structure.
    pub fn new_unchecked(buffer: T) -> Packet<T> {
        Packet { buffer }
    }

    /// Shorthand for a combination of [new_unchecked] and [check_len].
    ///
    /// [new_unchecked]: #method.new_unchecked
    /// [check_len]: #method.check_len
    pub fn new_checked(buffer: T) -> Result<Packet<T>> {
        let packet = Self::new_unchecked(buffer);
        packet.check_len()?;
        Ok(packet)
    }

    /// Ensure that no accessor method will panic if called.
    /// Returns `Err(Error::Truncated)` if the buffer is too short.
    ///
    /// The result of this check is invalidated by calling [set_hardware_len] or
    /// [set_protocol_len].
    ///
    /// [set_hardware_len]: #method.set_hardware_len
    /// [set_protocol_len]: #method.set_protocol_len
    #[allow(clippy::if_same_then_else)]
    pub fn check_len(&self) -> Result<()> {
        let len = self.buffer.as_ref().len();
        if len < HEADER_LEN {
            Err(Error::Truncated)
        } else {
            Ok(())
        }
    }

    /// Consume the packet, returning the underlying buffer.
    pub fn into_inner(self) -> T {
        self.buffer
    }

    #[inline]
    pub fn code(&self) -> Code {
        let data = self.buffer.as_ref();
        let raw = data[field::CODE];
        Code::from(raw)
    }

    /// Return the identification field.
    #[inline]
    pub fn identifier(&self) -> u8 {
        let data = self.buffer.as_ref();
        data[field::IDENT]
    }

    /// Return the password len field.
    #[inline]
    pub fn len(&self) -> u16 {
        let data = self.buffer.as_ref();
        NetworkEndian::read_u16(&data[field::LEN])
    }
}

impl<'a, T: AsRef<[u8]> + ?Sized> Packet<&'a T> {
    /// Return a pointer to the payload, without checking for 802.1Q.
    #[inline]
    pub fn data(&self) -> &'a [u8] {
        let data = self.buffer.as_ref();
        &data[field::DATA]
    }
}

impl<T: AsRef<[u8]> + AsMut<[u8]>> Packet<T> {
    /// Set the identification field.
    #[inline]
    pub fn set_code(&mut self, value: Code) {
        let data = self.buffer.as_mut();
        data[field::CODE] = value.into();
    }

    /// Set the password len field.
    #[inline]
    pub fn set_identifier(&mut self, value: u8) {
        let data = self.buffer.as_mut();
        data[field::IDENT] = value;
    }

    /// Set the password content field.
    #[inline]
    pub fn set_len(&mut self, value: u16) {
        let data = self.buffer.as_mut();
        NetworkEndian::write_u16(&mut data[field::LEN], value)
    }

    #[inline]
    pub fn data_mut(&mut self) -> &mut [u8] {
        let data = self.buffer.as_mut();
        &mut data[field::DATA]
    }
}

impl<T: AsRef<[u8]>> AsRef<[u8]> for Packet<T> {
    fn as_ref(&self) -> &[u8] {
        self.buffer.as_ref()
    }
}

// use crate::wire::{EthernetAddress, Ipv4Address};

/// A high-level representation of an Address Resolution Protocol packet.
#[derive(Debug, PartialEq, Eq, Clone, Copy)]
#[cfg_attr(feature = "defmt", derive(defmt::Format))]
#[non_exhaustive]
pub enum Repr {
    /// An Ethernet and IPv4 Address Resolution Protocol packet.
    EthernetEAP {
        code: Code,
        identifier: u8,
        len: u16,
    },
}

impl Repr {
    /// Parse an Address Resolution Protocol packet and return a high-level representation,
    /// or return `Err(Error::Unrecognized)` if the packet is not recognized.
    pub fn parse<T: AsRef<[u8]>>(packet: &Packet<T>) -> Result<Repr> {
        match packet.code() {
            Code::Request => Ok(Repr::EthernetEAP {
                code: packet.code(),
                identifier: packet.identifier(),
                len: packet.len(),
            }),
            Code::Response => Ok(Repr::EthernetEAP {
                code: packet.code(),
                identifier: packet.identifier(),
                len: packet.len(),
            }),
            Code::Success => Ok(Repr::EthernetEAP {
                code: packet.code(),
                identifier: packet.identifier(),
                len: packet.len(),
            }),
            Code::Failure => Ok(Repr::EthernetEAP {
                code: packet.code(),
                identifier: packet.identifier(),
                len: packet.len(),
            }),
            _ => Err(Error::Unrecognized),
        }
    }

    /// Return the length of a packet that will be emitted from this high-level representation.
    pub fn buffer_len(&self) -> usize {
        match *self {
            Repr::EthernetEAP { len, .. } => len as usize,
        }
    }

    /// Emit a high-level representation into an Address Resolution Protocol packet.
    pub fn emit<T: AsRef<[u8]> + AsMut<[u8]>>(&self, packet: &mut Packet<T>) {
        match *self {
            Repr::EthernetEAP {
                code,
                identifier,
                len,
            } => {
                packet.set_code(code);
                packet.set_identifier(identifier);
                packet.set_len(len);
            }
        }
    }
}

impl<T: AsRef<[u8]>> fmt::Display for Packet<T> {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match Repr::parse(self) {
            Ok(repr) => write!(f, "{}", repr),
            _ => {
                write!(f, "EAP (unrecognized)")?;
                write!(
                    f,
                    " code={:?} identifier={:?} len={:?} ",
                    self.code(),
                    self.identifier(),
                    self.len()
                )?;
                Ok(())
            }
        }
    }
}

impl fmt::Display for Repr {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match *self {
            Repr::EthernetEAP {
                code,
                identifier,
                len,
            } => {
                write!(
                    f,
                    "EAP type=Ethernet+EAP code={:?} identifier = {:?} len={:?} ",
                    code, identifier, len
                )
            }
        }
    }
}

use crate::wire::pretty_print::{PrettyIndent, PrettyPrint};

impl<T: AsRef<[u8]>> PrettyPrint for Packet<T> {
    fn pretty_print(
        buffer: &dyn AsRef<[u8]>,
        f: &mut fmt::Formatter,
        indent: &mut PrettyIndent,
    ) -> fmt::Result {
        match Packet::new_checked(buffer) {
            Err(err) => write!(f, "{}({})", indent, err),
            Ok(packet) => write!(f, "{}{}", indent, packet),
        }
    }
}

#[cfg(test)]
mod test {
    use super::*;

    static PACKET_BYTES: [u8; 7] = [0x02, 0xc6, 0x00, 0x07, 0x01, 0x63, 0x61];

    static DATA_BYTES: [u8; 3] = [0x01, 0x63, 0x61];

    #[test]
    fn test_deconstruct() {
        let packet = Packet::new_unchecked(&PACKET_BYTES[..]);
        assert_eq!(packet.code(), Code::Response);
        assert_eq!(packet.identifier(), 198);
        assert_eq!(packet.len(), 7);
        assert_eq!(packet.data(), &DATA_BYTES[..]);
    }

    fn packet_repr() -> Repr {
        Repr::EthernetEAP {
            code: Code::Response,
            identifier: 198,
            len: 7u16,
        }
    }

    #[test]
    fn test_parse() {
        let packet = Packet::new_unchecked(&PACKET_BYTES[..]);
        let repr = Repr::parse(&packet).unwrap();
        assert_eq!(repr, packet_repr());
    }

    #[test]
    fn test_construct() {
        let mut bytes = vec![0xa5; 7];
        let mut packet = Packet::new_unchecked(&mut bytes);
        packet.set_code(Code::Response);
        packet.set_identifier(198);
        packet.set_len(7);
        packet.data_mut().copy_from_slice(&DATA_BYTES[..]);
        assert_eq!(&packet.into_inner()[..], &PACKET_BYTES[..]);
    }
}
